package pnca;

import java.io.IOException;
import ucar.ma2.*;
import ucar.nc2.*;
import java.util.AbstractMap.*;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

/**
 * ResultsAdapter Stores results from analytical controller to NetCDF formatted
 * files
 *
 * @author Niko Simonson (Primary)
 * @author Brett Yasutake (Contributing)
 */
public class ResultsAdapter {

    // Constants
    public static final int DOUBLE = 0;
    public static final int INT = 1;
    public static final int BOOL = 2;
    // Variables
    private int type;                       // every variable must be the same type
    private String name;
    SimpleEntry<String, Integer[]>[] vars;  // name of variables, ranges of variable
    private NetcdfFileWriteable currentFile;
    private String source;

    /**
     * Adapter to write analysis results to desired output destination (NetCDF
     * file)
     *
     * @param varType       data type of values
     * @param fName         results output file name
     * @param sourceName    source file name to retrieve data provenance from
     * @param variables     array of SimpleEntry objects, each entry containing a
     *                      variable name and its values
     * @throws Exception  
     */
    public ResultsAdapter(int varType, String fName, String sourceName, SimpleEntry<String, Integer[]>... variables) throws Exception {
        vars = variables;
        type = varType;
        name = fName;
        source = sourceName;

        // replace invalid file name characters if OS is Windows
        if (System.getProperty("os.name").contains("Windows") || System.getProperty("os.name").contains("WINDOWS")) {
            name = name.replace('\\', '_');
            name = name.replace('/', '_');
            name = name.replace(':', '-');
            name = name.replace('*', '-');
            name = name.replace('?', '-');
            name = name.replace('"', '-');
            name = name.replace('<', '-');
            name = name.replace('>', '-');
            name = name.replace('|', '_');
        }

        create();
    }

    /**
     * File creation only (does not fill with values).
     */
    private void create() throws Exception {
        try {
            // Create new file
            currentFile = NetcdfFileWriteable.createNew(name);
            NetcdfFile sourceFile = NetcdfFile.open(source, null);

            List<Attribute> globals = sourceFile.getGlobalAttributes();
            Iterator<Attribute> giter = globals.iterator();
            Attribute gatt;

            while (giter.hasNext()) {
                gatt = giter.next();
                currentFile.addGlobalAttribute(gatt);
            }

            Dimension[] currentDims = new Dimension[2];
            List<Dimension> dims = sourceFile.getDimensions();
            Iterator<Dimension> diter = dims.iterator();
            Dimension currentDim;
            ArrayList<Dimension> varDims = new ArrayList<Dimension>();

            while (diter.hasNext()) {
                currentDim = diter.next();
                currentFile.addDimension(currentDim.getName(), currentDim.getLength());
                if (currentDim.getName().equals("Time")) {
//                    varDims.add(currentDim);
                } else if (currentDim.getName().equals("south_north")) {
                    varDims.add(currentDim);
                } else if (currentDim.getName().equals("west_east")) {
                    varDims.add(currentDim);
                }

            }

//            currentDims[0] = sourceFile.findDimension("Time");
            currentDims[0] = sourceFile.findDimension("south_north");
            currentDims[1] = sourceFile.findDimension("west_east");
            // Configure the new file's dimensions
//            int dimLength = vars[0].getValue().length;

//            for (int i = 0; i < dimLength; ++i) {
//                currentDims[i] = currentFile.addDimension("Dimension" + i, vars[0].getValue()[i]);
//            }



//            currentDims[0] = currentFile.addDimension("south_north", vars[0].getValue()[0]);

//            currentDims[1] = currentFile.addDimension("west_east", vars[0].getValue()[1]);
//            currentDims[2] = currentFile.addDimension("Time", -1, false, true, true);

            // Configure the new file's variables
            String[] varNames = new String[vars.length];
            for (int i = 0; i < vars.length; ++i) {
                varNames[i] = vars[i].getKey();
//                currentFile.addVariable(varNames[i], getType(), currentDims);  
                currentFile.addVariable(varNames[i], getType(), currentDims);
            }
            currentFile.create();
            currentFile.close();
        } catch (Exception e) {
            throw e;
        }
    }

    /**
     * Returns this provenance adapter data type.
     *
     * @return DataType - represents this results adapter data type
     */
    private DataType getType() throws Exception {
        switch (type) {
            case DOUBLE:
                return DataType.DOUBLE;
            case INT:
                return DataType.INT;
            case BOOL:
                return DataType.INT;
            default:
                return DataType.DOUBLE;
        }
    }

    /**
     * Writes a 2-dimensional double array variable to the output file.
     *
     * @param targetName target variable to write values to
     * @param values variable values to write to target variable
     * 
     * @throws Exception  
     */
    public void log2DDouble(String targetName, double[][] values) throws Exception {

        // Retreive target variable's range for each dimension
        Integer[] ranges = new Integer[2];
        for (int i = 0; i < vars.length; ++i) {
            if (vars[i].getKey() == null ? targetName == null : vars[i].getKey().equals(targetName)) {
                ranges = vars[i].getValue();
            }
        }

        // NetCDF takes the ArrayDouble.D2 data structure to write values
        ArrayDouble.D2 inValues = new ArrayDouble.D2(ranges[0], ranges[1]);
        for (int i = 0; i < ranges[0]; ++i) {
            for (int j = 0; j < ranges[1]; ++j) {
                inValues.set(i, j, values[i][j]);
            }
        }
        // Write values to file in target variable
        try {
            currentFile = NetcdfFileWriteable.openExisting(name);
            currentFile.write(targetName, inValues);
            currentFile.close();
        } catch (IOException | InvalidRangeException e) {
            throw e;
        }
    }

    /**
     * Writes a 2-dimensional integer array variable to the output file.
     *
     * @param targetName target variable to write values to
     * @param values variable values to write to target variable
     * 
     * @throws Exception  
     */
    public void log2DInt(String targetName, int[][] values) throws Exception {

        // Retreive target variable's range for each dimension
        Integer[] ranges = new Integer[2];
        for (int i = 0; i < vars.length; ++i) {
            if (vars[i].getKey() == null ? targetName == null : vars[i].getKey().equals(targetName)) {
                ranges = vars[i].getValue();
            }
        }

        // NetCDF takes the ArrayDouble.D2 data structure to write values
        ArrayInt.D2 inValues = new ArrayInt.D2(ranges[0], ranges[1]);
        for (int i = 0; i < ranges[0]; ++i) {
            for (int j = 0; j < ranges[1]; ++j) {
                inValues.set(i, j, values[i][j]);
            }
        }
        // Write values to file in target variable
        try {
            currentFile = NetcdfFileWriteable.openExisting(name);
            currentFile.write(targetName, inValues);
            currentFile.close();
        } catch (Exception e) {
            throw e;
        }
    }

    /**
     * Writes a 2-dimensional boolean array variable to the output file.
     *
     * @param targetName target variable to write values to
     * @param values variable values to write to target variable
     * 
     * @throws Exception  
     */
    public void log2DBool(String targetName, boolean[][] values) throws Exception {

        // Retreive target variable's range for each dimension
        Integer[] ranges = new Integer[2];
        for (int i = 0; i < vars.length; ++i) {
            if (vars[i].getKey() == null ? targetName == null : vars[i].getKey().equals(targetName)) {
                ranges = vars[i].getValue();
            }
        }

        // NetCDF takes the ArrayDouble.D2 data structure to write values
        ArrayInt.D2 inValues = new ArrayInt.D2(ranges[0], ranges[1]);
        for (int i = 0; i < ranges[0]; ++i) {
            for (int j = 0; j < ranges[1]; ++j) {
                if (values[i][j]) {
                    inValues.set(i, j, 1);
                } else {
                    inValues.set(i, j, 0);
                }
            }
        }
        // Write values to file in target variable
        try {
            currentFile = NetcdfFileWriteable.openExisting(name);
            currentFile.write(targetName, inValues);
            currentFile.close();
        } catch (Exception e) {
            throw e;
        }
    }
}
